import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Map, GeoJSON, ZoomControl, Pane, Marker } from 'react-leaflet';
import L from 'leaflet';
import _ from 'lodash';
import mapLoader from './map-loader';
import Control from 'react-leaflet-control';
import SafeIcon from '../../images/icon/safe.png';
import LowIcon from '../../images/icon/low.png';
import MiddleIcon from '../../images/icon/middle.png';
import HighIcon from '../../images/icon/high.png';
import FatalIcon from '../../images/icon/fatal.png';
import AssetIcon from '../../images/icon/asset.png';
import openNewPage from '@util/open-new-page';
import './index.less';
import 'leaflet/dist/leaflet.css';

const defaultFeatureStyle = {
  stroke: true,
  color: '#39476a',
  fill: true,
  fillColor: '#293247',
  weight: 1
};

const highLightFeatureStyle = {
  stroke: true,
  color: '#beccff',
  fill: true,
  fillColor: '#4F6283',
  weight: 2
};

const chinaBounds = [[18.21826171875003, 73.60732421875], [53.546533203124994, 134.75234375]];

const compilePopupTemplate = _.template(`<div class='title'>
<img src="${AssetIcon}" /><%=title%></div>
<div class='item'><%=location%></div>
<div class='item'>资产数: <%=siteCount%></div>
<div class='item'>漏洞影响资产数: <%=vulAffectedSiteCount%></div>`);

const levelIconMap = {
  safe: SafeIcon,
  low: LowIcon,
  middle: MiddleIcon,
  high: HighIcon,
  fatal: FatalIcon
};

const levelTitleMap = {
  safe: '安全',
  fatal: '严重',
  high: '高危',
  middle: '中危',
  low: '低危'
};

const allChildFeatureLevelMap = {
  world: [],
  country: ['region', 'city'],
  region: ['city'],
  city: []
};

class LeafletMap extends Component {
  static propTypes = {
    points: PropTypes.array,
    locationFilter: PropTypes.object,
    locationFilterStatus: PropTypes.bool
  };
  constructor(props) {
    super(props);
    this.state = {
      worldGeojsons: {},
      countryGeojsons: {},
      regionGeojsons: {},

      // center: [29.71, 115.97],
      // zoom: 3,
      highLightName: ''
    };
    this.currentHighLightFeatureLayer = null;
    this.currentPopup = null;
    this.currentMarkerElement = null;
    this.mapRef = React.createRef(); // map:  this.mapRef.current.contextValue.map
    this.minZoom = 2;
    // 国家、区域、城市的layer
    // this.featureLayers = {
    //   country: {
    //     中国: {}
    //   },
    //   region: {
    //     '中国-四川': {}
    //   },
    //   city: {
    //     '中国-四川-成都': {}
    //   }
    // };
    this.featureLayers = { country: {}, region: {}, city: {} };
    this.labelMarkers = { country: [], region: [], city: [] };
    this.currentlocationFilterStatus = props.locationFilterStatus;
    this.focusFeature = _.throttle(this.focusFeature.bind(this), 500);
    // this.onZoomChange = _.throttle(this.onZoomChange.bind(this), 500);
  }
  componentDidMount() {
    this.setGeojson('world', 'world', 0);
    this.setGeojson('country', '中国', 0);
  }
  // wait参数作用是等待缩放动画完成后再添加地图数据
  setGeojson = (level, name, wait = 0) => {
    const type = `${level}Geojsons`;
    const currentData = _.get(this.state, `["${type}"]["${name}"]`);
    const currentExist = currentData && typeof currentData === 'object';

    return mapLoader.loadMap(`/maps/${level}/${name}.json`).then(data => {
      if (!data || typeof data !== 'object') {
        return;
      }
      window.setTimeout(() => {
        const allChildLevels = _.clone(_.get(allChildFeatureLevelMap, level, []));

        const allChildLevelMap = {};
        allChildLevels.forEach(childrenLevel => {
          allChildLevelMap[`${childrenLevel}Geojsons`] = {};
        });

        // 若设置的数据是当前显示的地图数据，则不清理下一级别的label
        if (currentExist) {
          allChildLevels.shift();
        }
        allChildLevels.forEach(childrenLevel => {
          const preLabels = _.get(this.labelMarkers, childrenLevel, []);
          preLabels.forEach(label => {
            if (label && typeof label.remove === 'function') {
              label.remove();
            }
          });
        });

        this.setState({
          // [type]: Object.assign({}, this.state[type], {
          //   [name]: data
          // }),
          ...allChildLevelMap,
          [type]: {
            [name]: data
          }
        });
      }, wait);
      // 等待地图数据加载完后再更新字体大小
      window.setTimeout(() => {
        this.updateLabelsStyle();
      }, wait + 50);
    });
  };
  /**
   * 为每个feature设置 beLevel 属性，加入到featureLayers中，并设置label
   * @param {*} feature feature
   * @param {*} featureLevel 当前feature级别: country | region | city
   * @param {*} featureNamePrefix feature 前缀: world | 中国 | 中国-四川
   * @param {*} layer layer
   * @param {*} that this
   */
  onEachFeature(feature, featureLevel, featureNamePrefix, layer, that) {
    _.set(feature, 'properties.beLevel', featureLevel);
    const name = _.get(feature, 'properties.chinese') || _.get(feature, 'properties.name');
    if (!name) {
      return;
    }
    if (featureNamePrefix === 'world') {
      that.featureLayers[featureLevel][name] = layer;
    } else {
      that.featureLayers[featureLevel][`${featureNamePrefix}-${name}`] = layer;
    }

    if (!_.get(this, 'leaflet.map') && !layer.getBounds) {
      return;
    }

    const labelMarkers = L.marker(layer.getBounds().getCenter(), {
      icon: L.divIcon({
        className: 'pylogn-label',
        html: `<span class="label-wrapper">${name}</span>`
      }),
      pane: 'map-pane-label'
    }).addTo(this.leaflet.map);
    layer.beLabel = labelMarkers;
    if (that.labelMarkers[featureLevel]) {
      that.labelMarkers[featureLevel].push(labelMarkers);
    }
  }
  getFeatureStyle = feature => {
    const highLightName = this.state.highLightName;
    if (highLightName && feature.properties.name === highLightName) {
      return highLightFeatureStyle;
    } else {
      return defaultFeatureStyle;
    }
  };
  onMouseoutGeojson = e => {
    const layer = e.layer;
    if (
      layer.isBeChecked ||
      _.get(this.currentHighLightFeatureLayer, '_leaflet_id') === layer._leaflet_id
    ) {
      return;
    }
    layer.setStyle(defaultFeatureStyle);
  };
  onMouseoverGeojson = e => {
    const featureLayer = e.layer;
    if (!featureLayer) {
      return;
    }
    featureLayer.setStyle(highLightFeatureStyle);
    if (typeof featureLayer.bringToFront === 'function') {
      featureLayer.bringToFront();
    }
  };
  highLightFeatureLayer = featureLayer => {
    if (!featureLayer) {
      return;
    }
    const currentHighLightFeatureLayer = this.currentHighLightFeatureLayer;
    if (currentHighLightFeatureLayer) {
      currentHighLightFeatureLayer.isBeChecked = false;
      currentHighLightFeatureLayer.setStyle(defaultFeatureStyle);
      if (currentHighLightFeatureLayer.beLabel && currentHighLightFeatureLayer.beLabel.getElement) {
        const labelElement = currentHighLightFeatureLayer.beLabel.getElement();
        if (labelElement.classList) {
          labelElement.classList.remove('active');
        }
      }
    }
    if (typeof featureLayer.bringToFront === 'function') {
      featureLayer.bringToFront();
    }
    featureLayer.isBeChecked = true;
    featureLayer.setStyle(highLightFeatureStyle);
    if (featureLayer.beLabel && featureLayer.beLabel.getElement) {
      const labelElement = featureLayer.beLabel.getElement();
      if (labelElement.classList) {
        labelElement.classList.add('active');
      }
    }
    this.currentHighLightFeatureLayer = featureLayer;
  };
  fitBounds = bounds => {
    const map = _.get(this.mapRef, 'current.contextValue.map');
    if (!map || !bounds) {
      return;
    }
    map.fitBounds(bounds);
  };
  focusFeature = featureLayer => {
    if (!featureLayer) {
      return;
    }
    if (typeof featureLayer.bringToFront === 'function') {
      featureLayer.bringToFront();
    }
    this.fitBounds(featureLayer.getBounds());
    this.highLightFeatureLayer(featureLayer);
    let level = _.get(featureLayer, 'feature.properties.beLevel', 'city');
    let name =
      _.get(featureLayer, 'feature.properties.chinese') ||
      _.get(featureLayer, 'feature.properties.name', '');
    this.setGeojson(level, name, 300);
  };
  onClickGeojson = e => {
    this.highLightFeatureLayer(e.layer);
  };
  onDbClickGeojson = e => {
    this.focusFeature(e.layer);
  };

  activeCurrentMarker = () => {
    if (this.currentMarkerElement && this.currentMarkerElement.classList) {
      this.currentMarkerElement.classList.add('active');
    }
  };
  closePopUp = () => {
    if (this.currentPopup && this.currentPopup.closePopup) {
      this.currentPopup.remove();
    }
  };
  onClosePopUp = () => {
    this.currentPopup = null;
    if (this.currentMarkerElement && this.currentMarkerElement.classList) {
      this.currentMarkerElement.classList.remove('active');
    }
    this.currentMarkerElement = null;
  };
  openPopUp = (e, point = {}, setView) => {
    const map = _.get(this.mapRef, 'current.contextValue.map');
    const latlng = _.get(e, 'latlng');
    const key = `${latlng.lat}-${latlng.lng}`;
    if (this.currentPopup && this.currentPopup.beKey === key) {
      // 更新 beMouseoutNotClose
      this.currentPopup.beMouseoutNotClose = setView;
      return;
    }
    if (!map || !latlng) {
      return;
    }
    const { country, region, city, level } = point;
    const pointLocation = [country, region, city].filter(Boolean).join('-') || '-';

    this.currentMarkerElement = e.target.getElement();
    this.activeCurrentMarker();
    this.currentPopup = L.popup({
      closeButton: false,
      offset: [-1, 8],
      className: level,
      autoPan: false
    });
    this.currentPopup.beKey = key;
    this.currentPopup.beMouseoutNotClose = setView;
    this.currentPopup
      .on('remove', this.onClosePopUp)
      .setLatLng(latlng)
      .setContent(
        compilePopupTemplate({
          level: level,
          title: levelTitleMap[level] || '安全',
          location: pointLocation,
          siteCount: _.get(point, 'siteCount', 0),
          vulAffectedSiteCount: _.get(point, 'vulAffectedSiteCount', 0)
        })
      )
      .openOn(map);
    const featureLayer = this.getLocationFeatureLayer(point);
    this.highLightFeatureLayer(featureLayer);
    if (setView) {
      map.setView(
        {
          lat: latlng.lat + 2.5,
          lng: latlng.lng
        },
        5
      );
    }
  };
  resetBounds = () => {
    this.fitBounds(chinaBounds);
  };

  getLocationFeatureLayer = locationObj => {
    let { country, region, city } = locationObj;
    let featureLayer = null;
    if (!featureLayer && country && region && city) {
      featureLayer = _.get(this.featureLayers, `city["${country}-${region}-${city}"]`);
      city = void 0;
    }
    if (!featureLayer && country && region) {
      featureLayer = _.get(this.featureLayers, `region["${country}-${region}"]`);
      region = void 0;
    }
    if (!featureLayer && country) {
      featureLayer = _.get(this.featureLayers, `country[${country}]`);
    }
    return featureLayer;
  };

  componentDidUpdate() {
    const currentLocationFilter = this.props.locationFilter;
    const currentlocationFilterStatus = this.props.locationFilterStatus;
    if (
      currentlocationFilterStatus !== this.currentlocationFilterStatus &&
      Object.values(currentLocationFilter).some(Boolean)
    ) {
      this.currentlocationFilterStatus = currentlocationFilterStatus;
      const featureLayer = this.getLocationFeatureLayer(currentLocationFilter);
      this.focusFeature(featureLayer);
      this.closePopUp();
    } else {
      if (this.currentHighLightFeatureLayer) {
        this.currentHighLightFeatureLayer.setStyle(highLightFeatureStyle);
        this.activeCurrentMarker();
      }
    }
  }
  renderGeojsons = (geojsons, paneNamePrefix, paneZIndex, featureLevel) => {
    const names = Object.keys(geojsons);
    const that = this;
    return names.map(name => {
      const data = _.get(geojsons, `["${name}"]`);
      if (!data) {
        return null;
      }
      return (
        <Pane key={name} name={`${paneNamePrefix}-${name}`} style={{ zIndex: paneZIndex }}>
          <GeoJSON
            onclick={this.onClickGeojson}
            ondblclick={this.onDbClickGeojson}
            onmouseover={this.onMouseoverGeojson}
            onmouseout={this.onMouseoutGeojson}
            key={name}
            data={data}
            onEachFeature={function(feature, layer) {
              that.onEachFeature.call(this, feature, featureLevel, name, layer, that);
            }}
            style={this.getFeatureStyle}
          />
        </Pane>
      );
    });
  };
  renderMarker = (point = {}) => {
    const { latitude, longitude, level } = point;
    const position = [latitude, longitude];
    if (!position.every(Boolean)) {
      return null;
    }
    const icon = levelIconMap[level] || SafeIcon;
    return (
      <Marker
        key={position.join('-')}
        position={position}
        icon={L.divIcon({
          html: `<div class="map-marker-icon"><img src="${icon}"/></div>`,
          iconSize: [16, 16],
          iconAnchor: [8, 0],
          className: `map-marker ${level}`
        })}
        onmouseover={e => this.openPopUp(e, point, false)}
        onmouseout={() => {
          if (this.currentPopup && !this.currentPopup.beMouseoutNotClose) {
            this.closePopUp();
          }
        }}
        // 100ms等待双击
        onclick={e => setTimeout(() => this.openPopUp(e, point, true), 100)}
        ondblclick={() => this.jumpToAssetList(point)}
      />
    );
  };
  jumpToAssetList = point => {
    const { country, region, city } = point;
    const filter = [
      city ? `exact:city="${city}"` : '',
      region ? `exact:region="${region}"` : '',
      country ? `exact:country="${country}"` : ''
    ]
      .filter(Boolean)
      .join(' && ');
    openNewPage(`/asset-manage?${encodeURIComponent(filter)}`);
  };
  updateLabelsStyle = rawZoom => {
    let zoom = rawZoom;
    if (!zoom) {
      zoom = _.get(this, 'mapRef.current.viewport.zoom', 0);
    }
    if (!zoom) {
      return;
    }
    const hidden = zoom <= this.minZoom;
    const zoomScaleMap = {
      country: {
        from: [2, 13],
        to: [0.85, 2]
      },
      region: {
        from: [2, 13],
        to: [0.6, 2]
      },
      city: {
        from: [2, 13],
        to: [0.6, 2]
      }
    };
    const getScanle = (zoom, transform) => {
      const { from, to } = transform;
      const more = ((zoom - from[0]) * (to[1] - to[0])) / (from[1] - from[0]);
      return Math.min(to[1], Math.max(to[0], to[0] + more));
    };
    ['country', 'region', 'city'].forEach(level => {
      const scale = getScanle(zoom, zoomScaleMap[level]);

      _.get(this.labelMarkers, level, []).forEach(label => {
        const labelElement = label.getElement();
        if (!labelElement) {
          return;
        }
        const labelWrapper = _.get(labelElement.getElementsByClassName('label-wrapper'), '0');
        if (!labelWrapper || !labelWrapper.style) {
          return;
        }
        labelWrapper.style.transform = `scale(${scale})`;
        if (hidden && labelWrapper.classList) {
          labelWrapper.classList.add('hidden');
        } else if (labelWrapper.classList) {
          labelWrapper.classList.remove('hidden');
        }
      });
    });
  };
  onZoomChange = e => {
    if (!e.target || !e.target.getZoom) {
      return;
    }
    const zoom = e.target.getZoom();
    this.updateLabelsStyle(zoom);
  };
  render() {
    const { points = [] } = this.props;
    const { worldGeojsons, countryGeojsons, regionGeojsons } = this.state;

    return (
      <Map
        className="leaf-map-content"
        minZoom={this.minZoom}
        maxZoom={13}
        setView={true}
        zoomControl={false}
        animate={true}
        bounds={chinaBounds}
        ref={this.mapRef}
        doubleClickZoom={false}
        onzoomend={this.onZoomChange}
      >
        <ZoomControl position={'topright'} className="map-zoom-control" attribution={null} />
        <Control position="topright" className="map-reset-control">
          <span className="reset" onClick={this.resetBounds}>
            A
          </span>
        </Control>
        <Pane name="map-pane-label" />

        {this.renderGeojsons(worldGeojsons, 'map-pane-world', 410, 'country')}
        {this.renderGeojsons(countryGeojsons, 'map-pane-region', 420, 'region')}
        {this.renderGeojsons(regionGeojsons, 'map-pane-city', 430, 'city')}

        {points.map(this.renderMarker)}
      </Map>
    );
  }
}

export default LeafletMap;
